﻿using System;
using System.Globalization;
using System.Threading;
using System.Windows.Forms;
using OpenTap.Plugins.Interfaces.ACPsu;
using OpenTap.Plugins.Interfaces.Common;

namespace OpenTap.Plugins.AcPsu
{

    [Display("Keysight AC6800 AC PSU driver", Group: "OpenTap.Plugins", Description: "Keysight AC6800 AC PSU driver")]
    public class AcPsuKeysight : AcPsuBase
    {
        private bool _inhibitHwSwOk;

        public enum EQuestionableStatRegBitMask
        {
            OverVoltage =       1, // OV   Overvoltage Protection. Output is disabled by the overvoltage protection.
            OverCurrent =       2, // OC   Overcurrent protection. Output is disabled by the overcurrent protection.
            HwFailure =         4, // HWF  Output is disabledby hardware failure
            LowVoltage =        8, // LV   Low voltage detected
            OverTemperature =  16, // OT   Output is disabledby the overtemperature protection
            Watchdog =         32, // WDOG Watchdog protection(causedby no SCPI IO activity over specifiedtime)
            Inhibit =          64, // INH  Remote inhibit is active
            CalibrationFail = 256, // CAL  Calibration failure
            Overpower =       512, // OP   Overpower protection
            PeakCurrent =    1024, // CL-PEAK Current Limit on PEAK (overloadstate)
            PowerLimit =     2048, // PL Power limit(overloadstate)
            RmsCurrent =     4096, // CL-RMS Current Limit on RMS(overloadstate)
            Sensefault =     8192, // SF Sense Fault protection
            MeasOverLoad =  16384  // MEAS-OVLD Measurement overload detected
        }

        public AcPsuKeysight()
        {
            // Initial parameters defaults with adding new test to TAP
            VoltRangeInit = (int)EVoltageRange.RangeLoV;       // Default
            RemoteInhibitModeInit = ERemoteInhibitMode.Latc;   // Default
            RemoteInhibitLevelInit = ERemoteInhibitLevel.High; // Default
            Name = "KsAC6800";
        }

        #region Settings
            [Display(Name: "Voltage Range", Group: "Initial Parameters", Order: 1,
                Description: "Voltage Range to be initialized(HIGH/LOW). Can be changed later.")]
            public EVoltageRange VoltageRange { get; set; }

            [Display(Name: "RI Mode", Group: "Initial Parameters", Order: 3,
                Description: "Remote Inhibit Mode configuration, which can NOT be changed with in running. \n" +
                         "LATC = The RI input latches the output in the protection shutdown state, which can only be cleared by SW. \n" +
                         "LIVE = The output state follows the state of the RI input.\n" +
                         "OFF  = The instrument ignores the RI input. Use this, if RI option(AC68BFIU) is not installed.")]
            public ERemoteInhibitMode RemoteInhibitModeInit { get; set; }

            [Display(Name: "RI Level", Group: "Initial Parameters", Order: 4,
                Description: "Remote Inhibit Level configuration (LOW/HIGH). \n" +
                             "Can NOT be changed with in running.")]
            public ERemoteInhibitLevel RemoteInhibitLevelInit { get; set; }
        #endregion

        /// <inheritdoc />
        public override void ClearOutputProtection()
        {
            // Query alarms
            var questStatRegRd = ScpiQuery<int>("STAT:QUES:COND?");
            var buffer = "Register Questionable Status(" + questStatRegRd + ") active bits: ";

            if (questStatRegRd > 0) // If active alarms => clear
            {
                var bits = Enum.GetValues(typeof(EQuestionableStatRegBitMask));
                foreach (var bit in bits)
                    if ((questStatRegRd & (int)bit) > 0)
                        buffer = buffer + bit + "(" + (int)bit + "), ";

                buffer = buffer.Trim();
                if (buffer.EndsWith(","))
                    buffer = buffer.TrimEnd(',');
                Log.Info(buffer);
            }

            // Remote Inhibit state check
            if (((questStatRegRd & (int)EQuestionableStatRegBitMask.Inhibit) > 0)
               && _inhibitHwSwOk && RemoteInhibitModeInit != ERemoteInhibitMode.Off)
            {
                buffer = "Safety Remote Inhibit is active/danger state. CLOSE the fixture.";
                Log.Info(buffer);

                var operatRespResult = MessageBox.Show(buffer, "Safety check info!",
                    MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
                if (operatRespResult != DialogResult.OK)
                {
                    buffer = "Safety check is cancelled by Operator.";
                    Log.Error(buffer);
                    throw new InvalidOperationException(buffer);
                }

                ScpiCommand("OUTP:PROT:CLE");
                WaitForOperationComplete();
                for (var i = 1; i < 513 && ((questStatRegRd & (int)EQuestionableStatRegBitMask.Inhibit) > 0); i = i * 2)
                {
                    Thread.Sleep(i);
                    questStatRegRd = ScpiQuery<int>("STAT:QUES:COND?");
                }

                if ((questStatRegRd & (int)EQuestionableStatRegBitMask.Inhibit) > 0) // Safety lock is in wrong state => Error
                {
                    buffer = "Error: The safety Remote Inhibit state is still danger";
                    Log.Error(buffer);
                    throw new InvalidOperationException(buffer);
                }
                else
                    Log.Info("Safety Remote Inhibit is in safe state and Protection clear done.");
            }
            else
            {
                ScpiCommand("OUTP:PROT:CLE");
                WaitForOperationComplete();
                Log.Info("Protection clear done.");
            }

            QueryErrWithExc(true, "ClearOutputProtection");
        }

        /// <inheritdoc />
        public override double GetCurrentLimitDc()
        {
            return ScpiQuery<double>("CURR:OFFS?");
        }

        /// <inheritdoc />
        public override double GetVoltageDcProtectionLimitLower()
        {
            return ScpiQuery<double>("VOLT:OFFS:LIM:LOW?");
        }

        /// <inheritdoc />
        public override double GetVoltageDcProtectionLimitUpper()
        {
            return ScpiQuery<double>("VOLT:OFFS:LIM:UPP?");
        }

        /// <inheritdoc />
        public override EState GetVoltageDcProtectionState()
        {
            return ScpiQuery<string>("VOLT:OFFS:LIM?") == "1" ? EState.On : EState.Off;
        }

        /// <inheritdoc />
        public override double GetVoltageLevelDc()
        {
            return ScpiQuery<double>("VOLT:OFFS?");
        }

        /// <inheritdoc />
        public override EVoltageMode GetVoltageMode()
        {
            var readModeStat = ScpiQuery<string>("OUTP:COUP?");

            switch (readModeStat)
            {
                case "AC":
                    return EVoltageMode.Ac;

                case "DC":
                    return EVoltageMode.Dc;

                // case "ACDC": NO need yet. Will be added when need occurs
                // return EVoltageMode.AcDc;

                default:
                    throw new InvalidOperationException("GetVoltageMode query answer(" + readModeStat + "). " +
                                                        "Wanted answers: AC or DC"); // AC, DC or ACDC"); NO need yet. Will be added when need occurs
            }
        }

        /// <inheritdoc />
        public override double MeasureCurrentDc()
        {
            var result = ScpiQuery<double>("MEAS:CURR:DC?");
            QueryErrWithExc(true, "MeasureCurrentDc: " + result);
            return result;
        }

        /// <inheritdoc />
        public override double MeasureVoltageDc()
        {
            return ScpiQuery<double>("MEAS:VOLT:DC?");
        }

        /// <inheritdoc />
        public override void Open()
        {
            if (IsConnected) throw new InvalidOperationException("Psu is already connected!");
            base.Open();
            var buffer = "Error: ";

            var idString = IdnString;
            if (!idString.Contains("Agilent,AC680") && !idString.Contains("Keysight,AC680"))
            {
                buffer = buffer + "Invalid IDN-query response('" + idString + "'). Wanted(Agilent,AC680 or Keysight,AC680)";
                Log.Error(buffer);
                throw new ApplicationException(buffer);
            }

            // Remote Inhibit module HW & SW check
            _inhibitHwSwOk = false;
            buffer = ScpiQuery("*OPT?");
            if (buffer.Contains("AC68BFIU")) // HW module available
            {
                if (idString.Contains("A.02.03.0127") || idString.Contains("A.02.10.0131")) // Firmwares to use ONLY! Specified by Tuukka Tavastsjerna
                {  // ToDo: Specify other suitable firmwares, after those are available!
                      _inhibitHwSwOk = true;
                      Log.Info("Remote Inhibit Option(AC68BFIU) is available with Firmwares: A.02.03.0127 and A.02.10.0131: " + idString);
                }else Log.Info("Remote Inhibit Option(AC68BFIU) HW is available, but wanted Firmware(A.02.03.0127 or A.02.10.0131) not found: " + idString);
            }else     Log.Info("Remote Inhibit Option(AC68BFIU) is not available.");

            Reset(); // Reset + ReadRangCritLimits
            SetVoltageRange(VoltRangeInit);
            ScpiCommand("OUTP:PROT:CLE");
            WaitForOperationComplete();

            if (!_inhibitHwSwOk) // Remote Inhibit HW module "AC68BFIU" is not available
            {
                if (RemoteInhibitModeInit == ERemoteInhibitMode.Off) // Inhibit check not wanted to use
                    return;

                // Remote Inhibit is wanted => Err
                buffer = "Error: Cannot set Remote Inhibit to " + RemoteInhibitModeInit + " - state, if RI-module option is not available.";
                Log.Error(buffer);
                throw new ApplicationException(buffer);
            }

            // Remote Inhibit HW module "AC68BFIU" is available Setup Check
            var remoteInhibitModeRead = ScpiQuery<ERemoteInhibitMode>("OUTP:INH:MODE?");
            if (remoteInhibitModeRead != RemoteInhibitModeInit)
            {
                buffer = "Error: Remote Inhibit Mode Read(" + remoteInhibitModeRead + ") Initial(" + RemoteInhibitModeInit + ").";
                Log.Error(buffer);
                throw new ApplicationException(buffer);
            }

            if (RemoteInhibitModeInit == ERemoteInhibitMode.Off) // Inhibit check not wanted to use
                return;

            // Remote Inhibit is on. Check polarity settings.
            buffer = ScpiQuery("DIG:PIN:POL?");
            if (buffer.Contains("POS") && RemoteInhibitLevelInit == ERemoteInhibitLevel.High) return;
            if (buffer.Contains("NEG") && RemoteInhibitLevelInit == ERemoteInhibitLevel.Low)  return;

            buffer = "ERRor: Inhibit Level query respond(" + buffer + ") Wanted(" +
                     (RemoteInhibitLevelInit == ERemoteInhibitLevel.High ? "POS" : "NEG") + ")";
            Log.Error(buffer);
            QueryErrWithExc(true, "Open:RemoteInhibitSetupCheck");
            throw new ApplicationException(buffer);
        }

        /// <inheritdoc />
        public override void Reset()
        {
            base.Reset();
            WaitForOperationComplete();
            QueryErrWithExc(true, "Reset");

            if (_inhibitHwSwOk)
            {
                // Remote Inhibit setup
                ScpiCommand("OUTP:INH:MODE " + RemoteInhibitModeInit);

                if (RemoteInhibitModeInit != ERemoteInhibitMode.Off)
                {
                      ScpiCommand("DIG:PIN:POL " + (RemoteInhibitLevelInit == ERemoteInhibitLevel.High ? "POS" : "NEG"));
                      Log.Info("Remote Inhibit set: " + RemoteInhibitModeInit + ", " + RemoteInhibitLevelInit);
                }else Log.Info("Remote Inhibit set: " + RemoteInhibitModeInit);

                WaitForOperationComplete();
                QueryErrWithExc(true, "Reset+RemoteInhibitSet");
            }

            // Reset can change mode, so CritLimits need to run again
            RangeLowSetVal = 0;
            RangeHiSetVal = 0;
            ReadRangCritLimits(VoltRangeInit, Convert.ToInt32(EVoltageRange.RangeLoV), Convert.ToInt32(EVoltageRange.RangeHiV));
        }

        /// <inheritdoc />
        public override void SetCurrent(double currentAmps)
        {
            ScpiCommand("CURR " + currentAmps.ToString("#.000", CultureInfo.InvariantCulture));
            QueryErrWithExc(true, "SetCurrent: " + currentAmps);
        }

        /// <inheritdoc />
        public override void SetCurrentDc(double currentAmps)
        {
            ScpiCommand("CURR:OFFS " + currentAmps.ToString("#.000", CultureInfo.InvariantCulture));
            QueryErrWithExc(true, "SetCurrentDc: " + currentAmps);
        }

        /// <inheritdoc />
        public override void SetVoltageDc(double voltageDc)
        {
            ScpiCommand("VOLT:OFFS " + voltageDc);
        }

        /// <inheritdoc />
        public override void SetVoltageDcProtectionLimitLower(double voltageDcLimitLow)
        {
            ScpiCommand("VOLT:OFFS:LIM:LOW " + voltageDcLimitLow);
        }

        /// <inheritdoc />
        public override void SetVoltageDcProtectionLimitUpper(double voltageDcLimitUpp)
        {
            ScpiCommand("VOLT:OFFS:LIM:UPP " + voltageDcLimitUpp);
        }

        /// <inheritdoc />
        public override void SetVoltageDcProtectionState(EState onOff)
        {
            ScpiCommand("VOLT:OFFS:LIM " + onOff);
        }

        /// <inheritdoc />
        public override void SetVoltageMode(EVoltageMode voltageMode)
        {
            ScpiCommand("OUTP:COUP " + voltageMode);
            ScpiCommand("DISP:MET:COUP " + voltageMode);
        }
    }
}
